﻿namespace EasyWeb
{
    using System;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.CSharp.RuntimeBinder;

    internal static partial class EWHelpers
    {
        public static class ExpressionHelpers
        {
            public static LambdaExpression Lambda(Type instanceType, string memberPath, bool allowNull)
            {
                EWDebug.IsNotNull(instanceType, "instanceType");
                EWDebug.IsNotNullOrEmpty(memberPath, "memberPath");

                if (TypeHelpers.IsDynamicObject(instanceType))
                {
                    return LambdaDynamicProperty(instanceType, memberPath, allowNull);
                }

                return LambdaProperty(instanceType, memberPath, allowNull);
            }

            private static LambdaExpression LambdaDynamicProperty(Type instanceType, string memberPath, bool allowNull)
            {
                var parameterExpression = Expression.Parameter(instanceType, "item");
                Expression expression = parameterExpression;

                var memberNames = memberPath.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var memberName in memberNames)
                {
                    var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, memberName, typeof(ExpressionHelpers), new CSharpArgumentInfo[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                    });
                    expression = Expression.Dynamic(binder, typeof(object), expression);
                }

                return Expression.Lambda(expression, parameterExpression);
            }

            private static LambdaExpression LambdaProperty(Type instanceType, string memberPath, bool allowNull)
            {
                var parameterExpression = Expression.Parameter(instanceType, "item");
                Expression expression = parameterExpression;

                var memberNames = memberPath.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var memberName in memberNames)
                {
                    MemberInfo memberInfo = null;

                    foreach (var type in TypeHelpers.GetSelfAndBaseTypes(expression.Type))
                    {
                        var memberInfos = type.FindMembers(MemberTypes.Field | MemberTypes.Property, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance, Type.FilterNameIgnoreCase, memberName);
                        if (memberInfos != null && memberInfos.Length > 0)
                        {
                            memberInfo = memberInfos[0];
                            break;
                        }
                    }

                    expression = Expression.MakeMemberAccess(expression, memberInfo);
                }

                if (allowNull)
                {
                    expression = MakeExpressionAllowNull(expression);
                }

                return Expression.Lambda(expression, parameterExpression);
            }

            private static Expression MakeExpressionAllowNull(Expression memberAccess)
            {
                var defaultValue = DefaltValueExpression(memberAccess.Type);
                return LiftMemberAccessToNullRecursive(memberAccess, memberAccess, defaultValue);
            }

            private static Expression LiftMemberAccessToNullRecursive(Expression memberAccess, Expression conditional, Expression defaultValue)
            {
                var instance = GetInstanceExpressionFromExpression(memberAccess);
                if (instance == null)
                {
                    return conditional;
                }
                conditional = CreateIfNullExpression(instance, conditional, defaultValue);
                return LiftMemberAccessToNullRecursive(instance, conditional, defaultValue);
            }

            private static Expression GetInstanceExpressionFromExpression(Expression memberAccess)
            {
                var memberExpression = memberAccess as MemberExpression;
                if (memberExpression != null)
                {
                    return memberExpression.Expression;
                }
                var methodCallExpression = memberAccess as MethodCallExpression;
                if (methodCallExpression != null)
                {
                    return methodCallExpression.Object;
                }
                return null;
            }

            private static Expression CreateIfNullExpression(Expression instance, Expression memberAccess, Expression defaultValue)
            {
                if (ShouldGenerateCondition(instance.Type))
                {
                    return CreateConditionExpression(instance, memberAccess, defaultValue);
                }
                return memberAccess;
            }

            private static Expression DefaltValueExpression(Type type)
            {
                return Expression.Constant(GetDefaultValue(type), type);
            }

            private static bool ShouldGenerateCondition(Type type)
            {
                return !type.IsValueType || IsNullableType(type);
            }

            private static Expression CreateConditionExpression(Expression instance, Expression memberAccess, Expression defaultValue)
            {
                var nullExpression = DefaltValueExpression(instance.Type);
                var isNotNullExpression = Expression.NotEqual(instance, nullExpression);
                return Expression.Condition(isNotNullExpression, memberAccess, defaultValue);
            }

            private static object GetDefaultValue(Type type)
            {
                if (type.IsValueType)
                {
                    return EWHelpers.TypeHelpers.Create(type);
                }
                return null;
            }

            private static bool IsNullableType(Type type)
            {
                return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
            }
        }
    }
}
